<?php
/**
 * Helper_Category 实现分类的数据的组织，结构的形成
 * @copyright Copyright (c) 2007 - 2008 GiTeam.com
 * @package Helper
 * @author 叶腾飞 jufeis@gmail.com
 * @version 2.0
 */
class Helper_Category implements IteratorAggregate {
	/**
	 * 分类数据的缓存名字，通常使用数据表的表名_根节点名。
	 *
	 * @var string
	 */
	private $_cacheName;

	/**
     * 分类数据的原始数组，直接从数据库读出后就存入该数组
     *
     * @var array
     */
	private $_originalData = array();
	
	/**
	 * 树的每个节点的参考位置
	 * @var  
	 */
	private $_treeReference;
	
	/**
     * 字段的相关配置
     *
     */
	private $_config = array(
		// 数据库的对应字段名 {{
		'VAR_ID'    => 'id',		//节点的ID字段
		'VAR_PID'   => 'pid',			//节点的父ID字段
		'VAR_NAME'   => 'name',			//节点的名称字段
		'VAR_TABLE'   => null,				//存储节点的表名
		'VAR_SEQUENCE'=> null,				//用于节点排序的字段
		// }}
		'ROOT_ID'   =>  0,					//根节点的ID号
		//存储额外信息的索引名 {{
		'VAR_PATH'	=>	'_path_',			//节点路径		
		'VAR_LEVEL'	=>'_level_',			//节点的深度	
		'VAR_CHILDREN_QTY'	=> '_children_qty_',//所有节点子的数量
		'VAR_CHILDREN'	=>'_children_',			//所有子节点的id号
		//}
		
	);
    /**
     * 实现迭代模式
     * @return ArrayObject
     */
    function getIterator() {
        return new ArrayObject($this->_originalData);
    }
	/**
     * 分类数据的树形节点，通过该类的内部方法，将原始数组形成节点树的形式
     *
     * @var array
     * @access private
     */
	private $_tree = null;


	/**
     * 构造函数，初始化一个分类 
     * @param $config array 配置数组
     * @return bool
     */
	function __construct($config = array())
	{
		if(!$this->_isValidConfig($config)) {
			trigger_error("config array contains invalid index", E_USER_ERROR);
		}
		//合并配置
		!empty($config) && $this->_config = array_merge($this->_config,$config);
	}
	
	
	/**
	 * 获取 _config 中的信息 
	 * 
	 * // 数据库的对应字段名 {{
	 * 'VAR_ID'    => 'id',		//节点的ID字段
	 * 'VAR_PID'   => 'pid',			//节点的父ID字段
	 * 'VAR_NAME'   => 'name',			//节点的名称字段
	 * 'VAR_TABLE'   => null,				//存储节点的表名
	 * 'VAR_SEQUENCE'=> null,				//用于节点排序的字段
	 * // }}
	 * 'ROOT_ID'   =>  0,					//根节点的ID号
	 * //存储额外信息的索引名 {{
	 * 'VAR_PATH'	=>	'_path_',			//节点路径		
	 * 'VAR_LEVEL'	=>'_level_',			//节点的深度	
	 * 'VAR_CHILDREN_QTY'	=> '_children_qty_',//所有节点子的数量
	 * 'VAR_CHILDREN'	=>'_children_',			//所有子节点的id号
	 * 
	 * @param string $varname
	 * @return mix
	 */
	function getConfig($varname=null){
		if($varname === null){
			return $this->_config;
		} else if(isset($this->_config[$varname])){
			return $this->_config[$varname];
		} else {
			return null;
		}
	}
	
	/**
	 * 检查是否config中的所有索引名都在_config中存在
	 *
	 * @param array $config
	 * @return bool
	 */
	private function _isValidConfig($config)
	{
		foreach ($config as $key => $null){
			if (!array_key_exists($key,$this->_config)) {
				return false;
			}
		}
		return true;
	}
	/**
     * 检查 配置中的字段是否都在数据中都存在。
     * @param &$data array
     * @return bool 
     */
	private function _isValidData(&$data)
	{
		if (!is_array($data)) {
			return false;
		}
		
		$row = current($data);
		$flag = true;
		
		if(empty($data)){
			return $flag;
		}
		
		$flag = isset( $row[ $this->_config['VAR_ID']  ] ) 
			  && isset( $row[ $this->_config['VAR_PID'] ] ) ;
		return $flag;
	}
	/**
     * 载入数据
     * @return void
     */
	public function loadData($data)
	{
		//检查数组是否包含必需字段
		if(!$this->_isValidData($data)){
			trigger_error("the data doesn't contain the necessary fields",E_USER_ERROR);
		}

		$this->_originalData = $data;

		//print_r($this->_originalData);die();
		
		//将分类的ID设置为数组的Key
		$this->_setIdToKey();
		//print_r($this->_originalData);die();

		//准备根节点
		$this->_prepareRootId();
		//print_r($this->_originalData);die();

		//形成树
		$this->_toTree();
		//print_r($this->_tree);die();

		//如果指定了的顺序字段，则先将数据按顺序排列
		$this->sortTreeBy();
		//print_r($this->_tree);die();
		
		//根据树来补充Path
		$this->_addExtraInfo();
		//print_r($this->_originalData);die();
		
		$this->_saveCache();
	}
	/**
	 * 保存分类数据到缓存
	 */
	private function _saveCache()
	{
		
	}
	/**
	 * 添加额外的信息到_originalDatas
	 * VAR_PATH, VAR_CHILDREN, VAR_CHILDREN_QTY
	 * 路径，     子节点列表，   子节点的数量
	 */
	private function _addExtraInfo()
	{
		$varPath = &$this->_config['VAR_PATH'];
		$varChildrenQty =  &$this->_config['VAR_CHILDREN_QTY'];
		$varChildren =  &$this->_config['VAR_CHILDREN'];
		$varLevel =  &$this->_config['VAR_LEVEL'];
		
		foreach ($this->_originalData as $id => &$row){
			//添加Path
			$row[$varPath] = $this->getPath($id,true,'string');
			
			//添加子分类数量
			$row[$varChildrenQty] = count($this->getChildren($id,false,true));
			//添加子分类ID列表
			$row[$varLevel] = count(explode(',',$row[$varPath]));
			
			//添加子分类ID列表
			$row[$varChildren] = $this->getChildren($id,false,true,'string');
		}
		;
	}
	
	/**
	 * 将数据按照规定的排序字段排序,
	 */
	function sortTreeBy($fieldName = "")
	{
		empty($fieldName)  && $fieldName = $this->_config['VAR_SEQUENCE'];
		!empty($fieldName) && $this->_sortTree($this->_tree,$fieldName);
	}

	/**
	 * 将数据按照规定的排序字段排序
	 * To be finished
	 */
	private function _sortTree(&$array,$fieldName)
	{
		/*
		$data = $this->_originalData;
		
		static $sortFunc;
		if(!$sortFunc){
			$funcCode = '
				global $data,$fieldName;
				$seqA = $data[$a][$fieldName];
				$seqB = $data[$b][$fieldName];
				if($seqA == $seqB)
					return 0;
				else 
					return $seqA < $seqB ? 1 : -1;
			';
			$sortFunc = create_function('$a,$b',$funcCode);
		}
		uasort($array,$sortFunc);
		foreach ($array as $cateId => &$subCate){
			if(is_array($subCate)){
				$this->_sortTree($subCate,$fieldName);
			}
		}
		*/

	}

	/**
	 * 如果没有根节点的信息，则添加上
	 */
	private function _prepareRootId()
	{
		if(!isset($this->_originalData[$this->_config['ROOT_ID']])){
			$this->_originalData[$this->_config['ROOT_ID']] = array(
			$this->_config['VAR_ID']	=>	$this->_config['ROOT_ID'],
			$this->_config['VAR_PID']	=>	null,
			$this->_config['VAR_NAME']	=>	"根节点",
			);
		} else {
			$this->_originalData[$this->_config['ROOT_ID']][$this->_config['VAR_PID']] = null;
		}
	}

	/**
	 * 将分类的ID作为数组的索引值
	 */
	private function _setIdToKey()
	{
		$var_id = $this->_config['VAR_ID'];
		$new_array = array();
		foreach( $this->_originalData as $row){
			$new_array[$row[$var_id]] = $row;
		}
		$this->_originalData = $new_array;
	}

	/**
	 * 将原数组变成树,结果存在_tree中
	 */
	private function _toTree()
	{
		$idRefer = &$this->_treeReference;
		$varPid = $this->_config['VAR_PID'];
		$cateArr = &$this->_originalData;
		$tree = array();
		
		foreach( array_keys($cateArr) as $id ){
			$tree[$id] = "leaf";
		}
		
		foreach ($cateArr as $id => $row) {
			$pid = $row[$varPid];
			if ($pid !== null) {
				//检查父ID是否已经有了reference
				isset($idRefer[$pid])
				? $pointer = &$idRefer[$pid]
				: $pointer = &$tree[$pid];
				
				//初始化$tree[$pid]为数组
				$pointer == "leaf" && $pointer = array();
				
				//
				$pointer[$id] = $tree[$id];
				
				//保存每个id在树中的reference
				$idRefer[$id] = & $pointer[$id];
				
				unset($tree[$id]);
			}else{
				$idRefer[$id] = $tree[$id];
			}
		}
		$root = $this->_config['ROOT_ID'];
		$this->_tree[$root] = &$idRefer[$root];
	}

	/**
	 * 遍历一个树的索引值
	 * @param array $tree 要遍历的树
	 * @return array
	 */
	private static function _allKeys(&$tree)
	{
		if(!is_array($tree) || count($tree) == 0 ) return array();
		foreach ($tree as $id => $value) {
			$result[] = $id;
			if(is_array($value)){
				$result = array_merge($result, self::_allKeys($value));
			}
		}
		return $result;
	}

	/**
     * 载入缓存
     * @return boolean
     */
	function loadCache(){
		return false;
	}
	/**
	 * 清除缓存
	 */
	function cleanCache(){
		
	}
	
	/**
	 * 获取以某个ID树根的树
	 * @param int $id
	 * @return tree
	 */
	function getTree($id = null){
		null == $id && $id = $this->_config['ROOT_ID'];
		return $this->_treeReference[$id];
	}
	/**
	 * 将输出的数组格式化
	 * @param array $array
	 * @param sting $format
	 * @return mix
	 */
	private function _formatArray($array,$format = "array"){
		$formatAvailable = array(
			"string",
			"array_simple",
			"array_detail",
			"option",
			"hash",
		);
		
		if(!in_array($format,$formatAvailable)){
			trigger_error(" format is invalid ", E_USER_ERROR);
		}
		
		//处理输出格式
		switch ($format) {
			case "string":
				$ret = @join(',',$array);
				break;
			case "array_simple":
				$ret = $array;
				break;
			case "array_detail":
				$ret = array();
				if(is_array($array)){
					foreach ($array as $id) {
						$ret[] = $this->_originalData[$id];
					}
				}
				break;
			case "option":
				if(is_array($array)){
					foreach ($array as $id) {
						$level = $this->_originalData[$id][$this->_config['VAR_LEVEL']] - 1;
						$ret[$id] = @str_repeat("| - ",$level) . $this->_originalData[$id][$this->_config['VAR_NAME']];
					}
				}
				break;
			case "hash":
				if(is_array($array)){
					foreach ($array as $id) {
						$ret[$id] = $this->_originalData[$id][$this->_config['VAR_NAME']];
					}
				}
		}
		
		return  $ret;
	}
	/**
     * 获取某个分类的子类
     *
     * @param int $id 要查询哪一个ID的子类
     * @param boolean $includeSelf 是否将自己包含在输出结果中
     * @param boolean $recursive 是否递归将子目录的子目录包含在输出结果中
     * @param string $format 结果输出的格式，
     * 							array 	数组
     * 							string	字符串
     * 							detail	以二维数组返回所有子类的详细信息
     * 
     * @return mix
     */
	function getChildren($id, $includeSelf=true, $recursive=false, $format = "array_simple")
	{
		//如果该分类不存在，返回false
		if( !isset($this->_originalData[$id])) {
			return false;
		}
		else {
			$idArray = array();
			//获取该目录的树
			$tree = $this->getTree($id);
			//对是否包含所有子节点做处理
			$idArray = $recursive ? 
					   self::_allKeys($tree): 
					   @array_keys($tree);
			//对是否包含自己做处理
			$includeSelf == true &&	array_unshift($idArray,$id);
			
			foreach ($idArray as $k=>$v){
				if(!isset($this->_originalData[$v])){
					unset($idArray[$k]);
				}
			}
			return $this->_formatArray($idArray,$format);
		}
	}

	/**
     * 获取分类的路径
     * @param int $id 要查询哪一个ID的路径
     * @param boolean $includeSelf 是否将自己包含在输出结果中
     * @param string $format 结果输出的格式
     * @return array
     */
	function getPath($id,$includeSelf=true,$format="array_detail")
	{
		
		$varPid = $this->_config['VAR_PID'];
		
		$path = array();
		
		//如果该分类不存在，返回空数组
		if( !isset($this->_originalData[$id]) ){
			return array();
		}
		
		$pid = $id;
		while ($pid !== null) {
			array_unshift($path, $pid);
			$pid = $this->_originalData[$pid][$varPid];
		}
		
	
		return $this->_formatArray($path,$format);
		
	}

	/**
	  * 删除树的节点
	  * 
	  * 
	  * !!!!BUG!!!!!!!
	  * 删除节点reference中时，其他reference中的对应条目不会被清空。
	  * 
	  * @param string|array $idList 要删除的节点列表，用逗号隔开的id串或数组
	  * @return void
	  */
	function removeNode($idList)
	{
		if (!is_array($idList)) {
			$idList = split(",",$idList);
		}
		foreach ($idList as $id){
			$this->_treeReference[$id] = null;
			unset($this->_treeReference[$id]);
			$this->_originalData[$id]= null;
			unset($this->_originalData[$id]);
		}
		
	}
	/**
	 * 获取父节点列表
	 * @param int $id
	 * @param bool $includeSelf
	 * @param bool $recursive
	 * @param string $format
	 * @return array
	 */
	function getParents($id, $includeSelf=true, $recursive=false, $format = "array_simple")
	{
		if ($recursive) {
			return $this->getPath($id,$includeSelf,$format);
		} else {
			$pid = $this->_originalData[$id][ $this->_config['VAR_PID'] ];
			
			return $this->_originalData[$pid];
		}

	}
	public function getData(){
		return $this->_originalData;
	}

}
